import {BeanFactory} from './BeanFactory';
import {ClassType, ObjectType} from '../../type';
import {BeanDefinition} from '../BeanDefinition';
import {BeanError} from '../../error/BeanError';
import {ObjectUtils} from '../../util/ObjectUtils';
import {ReflectUtils} from '../../reflect/ReflectUtils';
import {Component} from '../../decorator/Component';
import {ClassMetadata} from '../../decorator/meta';
import {DependencyLike} from '../DependencyLike';
import {AutowiredMetadata} from '../../decorator/Autowired';
import {StringUtils} from '../../util/StringUtils';


/**
 * Generic bean factory implement
 * @author Zhang
 * @date 2019/3/19 10:22
 */
export class GenericBeanFactory implements BeanFactory {
  /**
   * Bean容器，key为bean名称，value为Bean定义详情
   */
  private readonly beans: Map<string, BeanDefinition> = new Map();
  /**
   * 以类为key的Map，一个类对应多个bean定义
   */
  private readonly classesBeans: Map<ObjectType<any>, BeanDefinition[]> = new Map();

  constructor() {
    this.initComponent(<ClassMetadata<any>[]>ReflectUtils.getTargets(Component));
    this.solveDependencies();
  }

  /**
   * add bean definition
   * @param definition {@link BeanDefinition}
   */
  addBeanDefinition(definition: BeanDefinition) {
    if (this.beans.has(definition.name)) {
      const exists = this.beans.get(definition.name);
      throw new BeanError(`重名了：${definition.name}，已有定义：名称:'${exists.name}', 类型:'${definition.beanClass.name}'`);
    }
    this.beans.set(definition.name, definition);

    let definitions = this.classesBeans.get(definition.beanClass);
    if (!definitions) {
      definitions = [];
    }
    definitions.push(definition);
    this.classesBeans.set(definition.beanClass, definitions);
  }

  containsBean(name: string): boolean {
    return this.beans.has(name);
  }

  /**
   * 根据名称查找Bean
   * @param name
   * @param required
   */
  private getBeanByName(name: string, required = true): any {
    const definition = this.beans.get(name);
    if (definition) {
      return ObjectUtils.isNotNull(definition.instance) ? definition.instance : this.createBean(definition);
    }
    if (required) {
      throw new BeanError(`没有找到名为 '${name}' 的实例`);
    }
    return null;
  }

  /**
   * 根据类型查找Bean
   * @param clazz
   * @param required
   */
  private getBeanByClass<T>(clazz: ClassType<T>, required = true): T {
    const definitions = this.classesBeans.get(clazz);
    if (definitions && definitions.length > 0) {
      let definition: BeanDefinition;
      if (definitions.length === 1) {
        definition = definitions[0];
      } else {
        definition = definitions.find(b => b.primary);
        if (!definition) {
          throw new BeanError(`根据类型: '${clazz.name}'找到了${definitions.length}个对象，但是没有一个是主候选对象！请将其中一个设置为'primary'，或使用名称注入。`);
        }
      }
      return ObjectUtils.isNotNull(definition.instance) ? definition.instance : this.createBean(definition);
    }
    if (required) {
      throw new BeanError(`根据类型: '${clazz.name}'，找不到对象！`);
    }
    return null;
  }

  /**
   * 创建Bean实例
   * @param definition
   */
  createBean(definition: BeanDefinition): any {
    let bean;
    if (definition.isSingleton()) {
      // 单例对象
      bean = new definition.beanClass();
    } else {
      // 原型对象，使用get访问器
      bean = function() {
        return new definition.beanClass();
      };
    }
    Object.defineProperty(definition, 'instance', ObjectUtils.readonlyPropDescripter(bean));
    return bean;
  }

  getBean(name: string): any;
  getBean<T>(requiredType: ClassType<T>): T;

  getBean(nameOrClass): any {
    if (typeof nameOrClass === 'string') {
      return this.getBeanByName(nameOrClass);
    } else if (typeof nameOrClass === 'function') {
      return this.getBeanByClass(nameOrClass);
    } else {
      throw new BeanError(`Not support argument type: ${typeof nameOrClass}`);
    }
  }

  /**
   * 初始化Component
   * @param targets
   */
  private initComponent(targets: ClassMetadata<any>[]): void {
    if (!targets || targets.length === 0) {
      return;
    }

    for (const target of targets) {
      const definition = new BeanDefinition();
      definition.scope = target.options.scope;
      definition.beanClass = target.clazz as ClassType<any>;
      definition.name = target.options.name;

      const dependencies = <AutowiredMetadata[]>ReflectUtils.getMetadata('summer:class:dependency', target.clazz);
      if (!!dependencies) {
        definition.dependsOn = dependencies.map(dep => (<DependencyLike>{
          propertyName: dep.propertyName,
          beanName: dep.beanName,
          beanType: dep.beanType,
          required: dep.required,
        }));
      }
      this.addBeanDefinition(definition);
    }
  }

  /**
   * 解决实例之间的依赖关系
   * 解决依赖的顺序为 指定的依赖Bean名称 > 属性名 > 属性类型
   */
  private solveDependencies(): void {
    this.beans.forEach(bean => {
      if (!!bean.dependsOn && bean.dependsOn.length > 0) {

        const instance = this.getBeanByName(bean.name);

        for (const dep of bean.dependsOn) {
          let depInstance;
          if (StringUtils.isNotBlank(dep.beanName)) {
            depInstance = this.getBeanByName(dep.beanName, dep.required);
          } else {
            depInstance = this.getBeanByClass(dep.beanType, false) || this.getBeanByName(dep.propertyName, dep.required);
          }

          Object.defineProperty(instance, dep.propertyName,
            typeof depInstance === 'function' ? {get: depInstance} :
              {
                value: depInstance,
                configurable: false,
                writable: false,
                enumerable: true,
              },
          );
        }
      }
    });
  }
}
